//
// Created by jiashu on 5/27/19.
//

#include <stdbool.h>
#include <stdio.h>
#include "expand_io.h"
#include "libsblk.h"
#include "nbdsrv.h"
#include "nbd-debug.h"
#include <semaphore.h>

struct expand_data {
    struct sblk *device;
};

static void end_io(struct sblk_rq *rq)
{
    sem_t *sem = rq->private;
    sem_post(sem);
}

static uint64_t clock_sample(void)
{
    struct timespec ts;

    clock_gettime(CLOCK_MONOTONIC, &ts);

    return (uint64_t) (ts.tv_nsec + ts.tv_sec * 1e9);
}

static int exp_read(struct expand_io *this, off_t offset, uint8_t *buf, size_t len)
{
    DEBUG("%s: Asked to read %u bytes at %llu.\n", __func__, (unsigned int) len, (unsigned long long) offset);

    sem_t sem;
    sem_init(&sem, 0, 0);

    struct sblk_rq rq = {
            .type = SBLK_RQ_IO,
            .opcode = SBLK_READ,
            .slba = offset / 512,
            .nlb = len / 512 - 1,
            .data = (void *) buf,
            .private = &sem,
            .end_io = end_io,
            .ns = 1,
    };

    uint64_t start = clock_sample();
    while (1) {
        if (0 == sblk_submit_io(this->private->device, &rq)) {
            break;
        }
    }

    sem_wait(&sem);
    uint64_t stop = clock_sample();
    DEBUG("%s: time=%f us\n", __func__, (stop - start) / (double)1e3);
    return 0;
}

static int exp_write(struct expand_io *this, off_t offset, const uint8_t *buf, size_t len, bool is_fua)
{
    (void) is_fua;//TODO need support fua flag.
    DEBUG("%s: Asked to write %u bytes at %llu.\n", __func__, (unsigned int) len, (unsigned long long) offset);

    sem_t sem;
    sem_init(&sem, 0, 0);

    struct sblk_rq rq = {
            .type = SBLK_RQ_IO,
            .opcode = SBLK_WRITE,
            .slba = offset / 512,
            .nlb = len / 512 - 1,
            .data = (void *) buf,
            .private = &sem,
            .end_io = end_io,
            .ns = 1,
    };

    uint64_t start = clock_sample();
    while (1) {
        if (0 == sblk_submit_io(this->private->device, &rq)) {
            break;
        }
    }

    sem_wait(&sem);
    uint64_t stop = clock_sample();
    DEBUG("%s: time=%f us\n", __func__, (stop - start) / (double)1e3);
    return 0;
}

static int exp_write_zeroes(struct expand_io *this, off_t offset, size_t len, bool is_fua)
{
    size_t maxsize = 64LL * 1024LL * 1024LL;
    uint8_t *buf = calloc(1, maxsize);

    while (len > 0) {
        size_t l = len;
        if (l > maxsize)
            l = maxsize;
        int ret = this->write(this, offset, buf, l, is_fua);
        if (ret) {
            free(buf);
            return ret;
        }
        len -= l;
    }
    free(buf);
}

static int exp_trim(struct expand_io *this, off_t offset, size_t len)
{
    (void) this;
    (void) offset;
    (void) len;
    return 0;
}

static int exp_flush(struct expand_io *this)
{
    (void) this;
    return 0;
}

static size_t exp_get_device_size(struct expand_io *this)
{
    return sblk_size_tu(this->private->device) * 4096;
}

static int exp_open(struct expand_io *this, const char *device_name)
{
    this->private = malloc(sizeof(struct expand_io));

    struct sblk_arg arg;
    strcpy(arg.blk_name, "sblk_host");
    strcpy(arg.dev_names[0], device_name);
    strcpy(arg.dev_types[0], "opennvm");
    arg.dev_count = 1;
    struct sblk *device = sblk_create(&arg);
    if (device == NULL) {
        free(this->private);
        return -1;
    }

    this->private->device = device;
    return 0;
}

static int exp_close(struct expand_io *this)
{
    sblk_close(this->private->device);
    free(this->private);
    return 0;
}

const struct expand_io expand_sblk = {
        .open = exp_open,
        .close = exp_close,
        .get_device_size = exp_get_device_size,
        .write = exp_write,
        .write_zeroes = exp_write_zeroes,
        .read = exp_read,
        .trim = exp_trim,
        .flush = exp_flush,
};
